﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.IO;
using System.Windows.Threading;
using System.Security.Cryptography;
using System.Threading;
using System.Windows.Forms;

namespace 文件同步工具
{
    public partial class MainWindow : Window
    {
        private char sep = Path.DirectorySeparatorChar;
        private string from;                // 来源
        private string dest;                // 去处
        private bool flag = false;          // 若 from = dest
        private bool stop = false;          // 终止标识
        private int check;                  // 文件校验限制
        private string[] exceptFrom;        // 排除来源
        private string[] exceptDest;        // 排除去处
        private string infoLog = "";        // 窗体日志
        private string speedLog = "";       // 速度日志
        private double logInterval = 0.1;   // 日志时间间隔
        private long TotalCount;             // 文件计数
        private long TotalSize;             // 总大小
        private long CopiedSize;            // 已复制大小
        private long preCopiedSize;         // 用于计算速度，上一个复制大小
        private DateTime preTime;           // 用于计算速度，上一个时间
        private DateTime nowTime;           // 用于计算速度，当前时间
        private List<string> failList;      // 若复制或移动文件失败，则记录

        public MainWindow()
        {
            InitializeComponent();
            // 日志，定时刷新
            DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(logInterval) };
            timer.Tick += 窗体日志;
            timer.Start();
        }
        private void 窗体日志(object sender, EventArgs e)
        {
            日志.Text = infoLog;
            速度.Text = speedLog;
        }
        // 对应页面“开始同步”按钮
        private void 开始同步(object sender, RoutedEventArgs e)
        {
            stop = flag = false;
            if (获取参数())
            {
                Task t = new Task(同步);
                t.Start();
            }
        }
        // 对应页面“结束同步”按钮
        private void 终止同步(object sender, RoutedEventArgs e)
        {
            stop = true;
        }
        private bool 获取参数()
        {
            if (!(from = 来源.Text).EndsWith(sep)) { from += sep; }
            if (!(dest = 去处.Text).EndsWith(sep)) { dest += sep; }
            if (dest.Equals(from)) { flag = true; }
            if (from.Trim().Equals(sep) || dest.Trim().Equals(sep)) {
                infoLog = "未输入路径！！";
                return false;
            }
            try { Directory.Exists(from); File.Exists(from); Directory.Exists(dest); File.Exists(dest); }
            catch {
                infoLog = "无路径访问权限！！";
                return false;
            }
            if (!Directory.Exists(from) || !new DirectoryInfo(dest).Root.Exists)
            {
                infoLog = "路径不存在！！";
                return false;
            }
            try { check = int.Parse(检查.Text); }
            catch { check = 0; }
            
            preTime = nowTime = DateTime.Now;
            TotalSize = TotalCount = preCopiedSize = CopiedSize = 0;

            exceptFrom = Regex.Split(Regex.Replace(排除.Text, "[\\s+\t+]", ""), "[,+，+]");
            exceptDest = new string[exceptFrom.Length];
            for (int i = 0; i < exceptFrom.Length; i++) { exceptFrom[i] = exceptFrom[i].Replace(dest, from); }
            for (int i = 0; i < exceptFrom.Length; i++)
            {
                if (!exceptFrom[i].StartsWith(from) || !(Directory.Exists(exceptFrom[i]) | File.Exists(exceptFrom[i]))) { exceptFrom[i] = "-"; }
                exceptDest[i] = exceptFrom[i].Replace(from, dest);
            }
            return true;
        }
        private void 获取文件列表(string path, List<string> list)
        {
            // 排除文件
            if (exceptFrom != null && exceptDest != null)
            {
                foreach (String s in exceptFrom) { if (path.StartsWith(s)) return; }
                foreach (String s in exceptDest) { if (path.StartsWith(s)) return; }
            }
            // 添加列表
            try { Directory.Exists(path); File.Exists(path); }
            catch { failList.Add(path); return; }
            if (Directory.Exists(path) && !((new DirectoryInfo(path).Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
            {
                string[] dirs = Directory.GetFileSystemEntries(path);
                foreach (string s in dirs) { 获取文件列表(s, list); }
            }
            else if (File.Exists(path) && !((new FileInfo(path).Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
            {
                list.Add(path);
                if (path.StartsWith(from))  // 只统计来源原文件夹的大小
                {
                    TotalCount++;
                    TotalSize += new FileInfo(path).Length;
                }
            } else
            {
                failList.Add(path);
            }
        }
        private void 同步()
        {
            infoLog = "适应性回路已启动，正在同步...";
            speedLog = "精算师已上线，正在统计工作量...";
            List<string> 待同步列表 = new List<string>();
            List<string> 已完成列表 = new List<string>();
            failList = new List<string>();  // 失败列表
            获取文件列表(from, 待同步列表);
            获取文件列表(dest, 已完成列表);
            speedLog = "文件数量：" + TotalCount + "\n总大小：" + (TotalSize >> 20) + " MB";

            /* 同步主过程 */
            if (flag) foreach (string s in 待同步列表) { 刷新文件(s); }
            else foreach (string s in 待同步列表) { 复制文件(s, s.Replace(from, dest)); }

            infoLog = "正在清理冗余文件......";
            speedLog = "不一致的文件存放于“" + dest + "\\冗余”文件夹下，请自行处理";
            清理无用文件(待同步列表, 已完成列表);
            清理空文件夹(dest);

            if (!stop) { infoLog = "文件同步完成"; }
            else { infoLog = "同步已中断"; }
            if(failList.Count > 0)
            {
                输出失败记录(failList);
                infoLog = "同步已结束\n失败记录在 " + from + " 查看";
            }
            Thread.Sleep(10000);
            infoLog = "点击以开始同步...";
            speedLog = "";
        }
        // 当“来源”与“去处”相同时，仅刷新文件
        
        private void 刷新文件(string s)
        {
            if (stop || s.EndsWith(".待同步")) { return; }
            string oldPath = s + ".待同步";
            try { File.Exists(s); File.Exists(oldPath); }
            catch { failList.Add(s); return; }
            // 如果之前出过差错，则会存在“.待同步”文件
            if (File.Exists(s)) {
                if (!File.Exists(oldPath)) {
                    try { File.Move(s, oldPath, true); }
                    catch {
                        failList.Add(s);
                        return;
                    }
                }
                try {
                    复制文件(oldPath, s);
                    File.Delete(oldPath);
                }
                catch {
                    failList.Add(s);
                    return;
                }
            }
        }
        // 复制文件（带校验）
        private void 复制文件(string oldPath, string newPath)
        {
            if (stop) { return; }
            try { File.Exists(oldPath); File.Exists(newPath); }
            catch
            {
                TotalCount--;
                failList.Add(oldPath);
                return;
            }
            infoLog = "正在同步 - " + oldPath.Replace(from, "" + sep);
            // 若    新文件 已存在 且                     文件长度 相同
            if (File.Exists(newPath) && new FileInfo(oldPath).Length == new FileInfo(newPath).Length)
            {
                infoLog += "\n正在检查..." + new FileInfo(oldPath).Name;
                // 若                        文件大于check        或                          （文件小于check        且 哈希相同）
                if (new FileInfo(oldPath).Length >= (check << 20) || GetMD5(oldPath) == GetMD5(newPath))
                {
                    TotalCount--;
                    CopiedSize += new FileInfo(oldPath).Length;
                    speedLog = "进度：" + string.Format("{0:N2}", 100.0 * CopiedSize / TotalSize) + "%\t\t剩余文件数：" + TotalCount;
                    return;
                }
            }
            DirectoryInfo parent = Directory.GetParent(newPath);
            if (parent != null) parent.Create();
            File.Copy(oldPath, newPath, true);

            // 同步完校验文件
            if (new FileInfo(oldPath).Length >= (check << 20) || GetMD5(oldPath) == GetMD5(newPath))
            {
                计算同步速度(oldPath);
            }
            else
            {
                File.Delete(newPath);
                复制文件(oldPath, newPath);
            }
        }

        // 清理冗余文件
        private void 清理无用文件(List<string> list1, List<string> list2)
        {
            foreach (string s in list2)
            {
                if (!list1.Contains(s.Replace(dest, from)))
                {
                    string sNew = s.Replace(dest, dest + "不一致的文件放在此文件夹" + sep);
                    DirectoryInfo parent = Directory.GetParent(sNew);
                    if (parent != null) parent.Create();
                    try { File.Move(s, sNew, true); }
                    catch { continue; }
                }
            }
        }
        // 清理空文件夹
        private void 清理空文件夹(string path)
        {
            if (stop) return;
            try { Directory.Exists(path); } catch { return; }
            if (Directory.Exists(path))
            {
                DirectoryInfo di = new DirectoryInfo(path);
                if (di.Name.Equals(".Trashes") || di.Name.Equals(".Spotlight - V100") ||
                    di.Name.Equals("$RECYCLE.BIN") || di.Name.Equals("System Volume Information"))
                { return; }
                string[] dirs = new string[] { "" };
                try { dirs = Directory.GetDirectories(path); } catch { return; }
                foreach (string s in dirs) { 清理空文件夹(s); }
                try { di.Delete(); }
                catch { return; }
            }
        }

        private void 计算同步速度(string oldPath)
        {
            TotalCount--;
            CopiedSize += new FileInfo(oldPath).Length;
            if (CopiedSize - preCopiedSize < 50 * 1024 * 1024) return;
            nowTime = DateTime.Now;
            TimeSpan ts = nowTime - preTime;
            double speed = (((CopiedSize - preCopiedSize) >> 20) / (ts.TotalMilliseconds) * 1000);
            if (speed > 2000)
            { speedLog = "进度：" + string.Format("{0:N2}", 100.0 * CopiedSize / TotalSize) + "%\t\t剩余文件数：" + TotalCount; }
            else
            { speedLog = "进度：" + string.Format("{0:N2}", 100.0 * CopiedSize / TotalSize) + "%\t\t剩余文件数：" + TotalCount + "\t\t速度：" + string.Format("{0:N2}", speed) + "MB/s"; }
            preCopiedSize = CopiedSize;
            preTime = DateTime.Now;
        }
        // 输出失败文件
        private void 输出失败记录(List<string> failList)
        {
            StreamWriter failTxt = File.CreateText(from + "失败记录.txt");
            failTxt.AutoFlush = true;
            foreach (string s in failList)
            {
                failTxt.WriteLine(s);
            }
        }

        private static string GetMD5(string path)
        {
            MD5 myMD5 = MD5.Create();
            FileStream fileStream = new FileInfo(path).Open(FileMode.Open);
            fileStream.Position = 0;
            byte[] hashValue = myMD5.ComputeHash(fileStream);
            fileStream.Close();
            return PrintByteArray(hashValue);
        }
        public static string PrintByteArray(byte[] array)
        {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < array.Length; i++)
            {
                result.Append($"{array[i]:X2}");
                if ((i % 4) == 3) result.Append(' ');
            }
            return result.ToString();
        }

        // 文件选择器
        private void 选择来源文件夹(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            if (System.Windows.Forms.DialogResult.OK == dialog.ShowDialog())
            {
                来源.Text = dialog.SelectedPath;
            }
        }
        private void 选择去处文件夹(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            if (System.Windows.Forms.DialogResult.OK == dialog.ShowDialog())
            {
                去处.Text = dialog.SelectedPath;
            }
        }
        private void 选择排除文件夹(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            if (System.Windows.Forms.DialogResult.OK == dialog.ShowDialog())
            {
                排除.Text += dialog.SelectedPath + ",";
            }
        }
    }
}
